#region

using System;
using System.Collections;
using System.Collections.Generic;

#endregion

namespace Json
{
    public class JsonData : IJsonWrapper, IEquatable<JsonData>
    {
        #region Fields

        private bool _instanceBoolean;
        private double _instanceDouble;
        private int _instanceInteger;
        private long _instanceLong;
        private string _instansString;

        private JsonDataDictionary _instanceDictionary;
        private JsonDataList _instanceArray;

        private JsonType _type;
        private string _typeDesciptor = string.Empty;

        #endregion

        #region Properties

        private bool IsArray
        {
            get { return _type == JsonType.Array; }
        }

        private bool IsBoolean
        {
            get { return _type == JsonType.Boolean; }
        }

        private bool IsDouble
        {
            get { return _type == JsonType.Double; }
        }

        private bool IsInt
        {
            get { return _type == JsonType.Int; }
        }

        private bool IsLong
        {
            get { return _type == JsonType.Long; }
        }

        private bool IsObject
        {
            get { return _type == JsonType.Object; }
        }

        private bool IsString
        {
            get { return _type == JsonType.String; }
        }

        #endregion

        #region IJsonWrapper Properties

        bool IJsonWrapper.IsObjectTypeSpecified
        {
            get { return !string.IsNullOrEmpty(_typeDesciptor); }
        }

        bool IJsonWrapper.IsValueType
        {
            get { return IsBoolean || IsDouble || IsInt || IsLong || IsString; }
        }

        bool IJsonWrapper.IsArray
        {
            get { return IsArray; }
        }

        bool IJsonWrapper.IsBoolean
        {
            get { return IsBoolean; }
        }

        bool IJsonWrapper.IsDouble
        {
            get { return IsDouble; }
        }

        bool IJsonWrapper.IsInt
        {
            get { return IsInt; }
        }

        bool IJsonWrapper.IsLong
        {
            get { return IsLong; }
        }

        bool IJsonWrapper.IsObject
        {
            get { return IsObject; }
        }

        bool IJsonWrapper.IsString
        {
            get { return IsString; }
        }

        #endregion
     
        #region Constructors

        public JsonData()
        {
        }

        public JsonData(bool boolean)
        {
            _type = JsonType.Boolean;
            _instanceBoolean = boolean;
        }

        public JsonData(double number)
        {
            _type = JsonType.Double;
            _instanceDouble = number;
        }

        public JsonData(int number)
        {
            _type = JsonType.Int;
            _instanceInteger = number;
        }

        public JsonData(long number)
        {
            _type = JsonType.Long;
            _instanceLong = number;
        }

        public JsonData(object obj)
        {
            if (obj is Boolean)
            {
                _type = JsonType.Boolean;
                _instanceBoolean = (bool) obj;
                return;
            }

            if (obj is Double)
            {
                _type = JsonType.Double;
                _instanceDouble = (double) obj;
                return;
            }

            if (obj is Int32)
            {
                _type = JsonType.Int;
                _instanceInteger = (int) obj;
                return;
            }

            if (obj is Int64)
            {
                _type = JsonType.Long;
                _instanceLong = (long) obj;
                return;
            }

            if (obj is String)
            {
                _type = JsonType.String;
                _instansString = (string) obj;
                return;
            }

            throw new ArgumentException(
                "Unable to wrap the given object with JsonData");
        }

        public JsonData(string str)
        {
            _type = JsonType.String;
            _instansString = str;
        }

        #endregion

        #region Implicit Conversions

        public static implicit operator JsonData(Boolean data)
        {
            return new JsonData(data);
        }

        public static implicit operator JsonData(Double data)
        {
            return new JsonData(data);
        }

        public static implicit operator JsonData(Int32 data)
        {
            return new JsonData(data);
        }

        public static implicit operator JsonData(Int64 data)
        {
            return new JsonData(data);
        }

        public static implicit operator JsonData(String data)
        {
            return new JsonData(data);
        }

        #endregion

        #region Explicit Conversions

        public static explicit operator Boolean(JsonData data)
        {
            if (data._type != JsonType.Boolean)
                throw new InvalidCastException(
                    "Instance of JsonData doesn't hold a double");

            return data._instanceBoolean;
        }

        public static explicit operator Double(JsonData data)
        {
            if (data._type != JsonType.Double)
                throw new InvalidCastException(
                    "Instance of JsonData doesn't hold a double");

            return data._instanceDouble;
        }

        public static explicit operator Int32(JsonData data)
        {
            if (data._type != JsonType.Int)
                throw new InvalidCastException(
                    "Instance of JsonData doesn't hold an int");

            return data._instanceInteger;
        }

        public static explicit operator Int64(JsonData data)
        {
            if (data._type != JsonType.Long)
                throw new InvalidCastException(
                    "Instance of JsonData doesn't hold an int");

            return data._instanceLong;
        }

        public static explicit operator String(JsonData data)
        {
            if (data._type != JsonType.String)
                throw new InvalidCastException(
                    "Instance of JsonData doesn't hold a string");

            return data._instansString;
        }

        #endregion

        #region IJsonWrapper Methods

        string IJsonWrapper.TypeDescriptor
        {
            get
            {
                return _typeDesciptor;                
            }

            set
            {
                _typeDesciptor = value;
            }
        }

        Type IJsonWrapper.GetValueType()
        {
            switch (_type)
            {
                case JsonType.None:
                    return null;
                case JsonType.Object:
                    return typeof (IDictionary<string, JsonData>);
                case JsonType.Array:
                    return typeof (IList<JsonData>);
                case JsonType.String:
                    return typeof (string);
                case JsonType.Int:
                    return typeof (int);
                case JsonType.Long:
                    return typeof (long);
                case JsonType.Double:
                    return typeof (double);
                case JsonType.Boolean:
                    return typeof (bool);
                default:
                    return null;
            }
        }

        object IJsonWrapper.GetValue()
        {
            switch (_type)
            {
                case JsonType.None:
                    return null;
                case JsonType.Object:
                    return _instanceDictionary;
                case JsonType.Array:
                    return _instanceArray;
                case JsonType.String:
                    return _instansString;
                case JsonType.Int:
                    return _instanceInteger;
                case JsonType.Long:
                    return _instanceLong;
                case JsonType.Double:
                    return _instanceDouble;
                case JsonType.Boolean:
                    return _instanceBoolean;
                default:
                    return null;
            }
        }

        bool IJsonWrapper.GetBoolean()
        {
            if (_type != JsonType.Boolean)
                throw new InvalidOperationException(
                    "JsonData instance doesn't hold a boolean");

            return _instanceBoolean;
        }

        double IJsonWrapper.GetDouble()
        {
            if (_type != JsonType.Double)
                throw new InvalidOperationException(
                    "JsonData instance doesn't hold a double");

            return _instanceDouble;
        }

        int IJsonWrapper.GetInt()
        {
            if (_type != JsonType.Int)
                throw new InvalidOperationException(
                    "JsonData instance doesn't hold an int");

            return _instanceInteger;
        }

        long IJsonWrapper.GetLong()
        {
            if (_type != JsonType.Long)
                throw new InvalidOperationException(
                    "JsonData instance doesn't hold a long");

            return _instanceLong;
        }

        string IJsonWrapper.GetString()
        {
            if (_type != JsonType.String)
                throw new InvalidOperationException(
                    "JsonData instance doesn't hold a string");

            return _instansString;
        }

        IList IJsonWrapper.GetList()
        {
            if (_type != JsonType.Array)
                throw new InvalidOperationException(
                    "JsonData instance doesn't hold a array");

            return _instanceArray;
        }

        IOrderedDictionary IJsonWrapper.GetDictionary()
        {
            if (_type != JsonType.Object)
                throw new InvalidOperationException(
                    "JsonData instance doesn't hold an object");

            return _instanceDictionary;
        }

        void IJsonWrapper.SetBoolean(bool val)
        {
            _type = JsonType.Boolean;
            _instanceBoolean = val;
        }

        void IJsonWrapper.SetDouble(double val)
        {
            _type = JsonType.Double;
            _instanceDouble = val;
        }

        void IJsonWrapper.SetInt(int val)
        {
            _type = JsonType.Int;
            _instanceInteger = val;
        }

        void IJsonWrapper.SetLong(long val)
        {
            _type = JsonType.Long;
            _instanceLong = val;
        }

        void IJsonWrapper.SetString(string val)
        {
            _type = JsonType.String;
            _instansString = val;
        }

        #endregion

        #region Internal Methods

        internal static JsonData ToJsonData(object obj)
        {
            if (obj == null)
                return null;

            if (obj is JsonData)
                return (JsonData) obj;

            return new JsonData(obj);
        }

        #endregion

        #region IEquatable<JsonData> Members

        public bool Equals(JsonData x)
        {
            if (x == null)
                return false;

            if (x._type != _type)
                return false;

            switch (_type)
            {
                case JsonType.None:
                    return true;

                case JsonType.Object:
                    return _instanceDictionary.Equals(x._instanceDictionary);

                case JsonType.Array:
                    return _instanceArray.Equals(x._instanceArray);

                case JsonType.String:
                    return _instansString.Equals(x._instansString);

                case JsonType.Int:
                    return _instanceInteger.Equals(x._instanceInteger);

                case JsonType.Long:
                    return _instanceLong.Equals(x._instanceLong);

                case JsonType.Double:
                    return _instanceDouble.Equals(x._instanceDouble);

                case JsonType.Boolean:
                    return _instanceBoolean.Equals(x._instanceBoolean);
            }

            return false;
        }

        #endregion

        #region IJsonWrapper Members

        public JsonType GetJsonType()
        {
            return _type;
        }

        public void SetJsonType(JsonType type)
        {
            if (_type == type)
                return;

            switch (type)
            {
                case JsonType.None:
                    break;

                case JsonType.Object:
                    _instanceDictionary = new JsonDataDictionary();
                    break;

                case JsonType.Array:
                    _instanceArray = new JsonDataList();
                    break;

                case JsonType.String:
                    _instansString = default (String);
                    break;

                case JsonType.Int:
                    _instanceInteger = default (Int32);
                    break;

                case JsonType.Long:
                    _instanceLong = default (Int64);
                    break;

                case JsonType.Double:
                    _instanceDouble = default (Double);
                    break;

                case JsonType.Boolean:
                    _instanceBoolean = default (Boolean);
                    break;
            }

            _type = type;
        }

        #endregion

        public override string ToString()
        {
            switch (_type)
            {
                case JsonType.Array:
                    return "JsonData array";

                case JsonType.Boolean:
                    return _instanceBoolean.ToString();

                case JsonType.Double:
                    return _instanceDouble.ToString();

                case JsonType.Int:
                    return _instanceInteger.ToString();

                case JsonType.Long:
                    return _instanceLong.ToString();

                case JsonType.Object:
                    return "JsonData object";

                case JsonType.String:
                    return _instansString;
            }

            return "Uninitialized JsonData";
        }
    }


    internal sealed class OrderedDictionaryEnumerator : IDictionaryEnumerator
    {
        private readonly IEnumerator<KeyValuePair<string, JsonData>> _listEnumerator;

        public OrderedDictionaryEnumerator(
            IEnumerator<KeyValuePair<string, JsonData>> enumerator)
        {
            _listEnumerator = enumerator;
        }

        #region IDictionaryEnumerator Members

        public object Current
        {
            get { return Entry; }
        }

        public DictionaryEntry Entry
        {
            get
            {
                KeyValuePair<string, JsonData> curr = _listEnumerator.Current;
                return new DictionaryEntry(curr.Key, curr.Value);
            }
        }

        public object Key
        {
            get { return _listEnumerator.Current.Key; }
        }

        public object Value
        {
            get { return _listEnumerator.Current.Value; }
        }


        public bool MoveNext()
        {
            return _listEnumerator.MoveNext();
        }

        public void Reset()
        {
            _listEnumerator.Reset();
        }

        #endregion
    }
}